/*********************************************************************************
 *      Copyright:  (C) 2023 xiemujie<1289180278@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  log.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2023年04月01日)
 *         Author:  xiemujie <1289180278@qq.com>
 *      ChangeLog:  1, Release initial version on "2023年04月01日 13时23分47秒"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include "log.h"

extern const char* __progname;
#define PROGRAM_NAME __progname

typedef struct log_s {
	FILE              *fp;
	int                loglevel;
	int                use_stdout;
	unsigned int       currsize;
	unsigned int       maxsize;
	int                bufsize;
	char               filename1[64];
} log_t;

static struct log_s g_log;

static const char *LOG_LEVELS[] = {
	LOG_STRING_ERROR,
	LOG_STRING_WARN,
	LOG_STRING_INFO,
	LOG_STRING_DEBUG
};

/*
 *Function Introduction: Initialize log
 *Input: *filename, loglevel
 *Output: g_log.loglevel, use_stdout, fp, bufsize, maxsize, currsize
 *Return Value: null 
 */
int log_init(char *filename, int loglevel)
{
	int   size;
	int   fd;

	memset(&g_log, 0, sizeof(g_log));

	log_term();

	g_log.loglevel = loglevel > LOG_LEVEL_MAX ? LOG_LEVEL_MAX : loglevel;

	if (!filename || !strcasecmp(filename, "stdout"))
	{
		g_log.use_stdout = 1;
		g_log.fp = stderr;
	}
	else
	{
		memset(g_log.filename1, 0,sizeof(g_log.filename1));
		snprintf(g_log.filename1, sizeof(g_log.filename1), "%s", filename);

		g_log.use_stdout = 0;
		g_log.fp = fopen(filename, "a");
		if (!g_log.fp)
		{
			fprintf(stderr, "Failed to open file '%s': %s", filename, strerror(errno));
			return -1;
		}

		g_log.bufsize = 0 >= g_log.bufsize ? LOG_BUF_MAX_LEN : g_log.bufsize;

		fd = fileno(g_log.fp);
		ftruncate(fd, 0);

		fseek(g_log.fp, SEEK_SET, SEEK_END);
		size = ftell(g_log.fp);

		g_log.maxsize = 0 >= g_log.maxsize ? LOG_FILE_SIZE : g_log.maxsize;
		g_log.currsize = (unsigned int)size >= 0 ? size : 0;
	}

	return 0;
}

/*
 *Function Introduction: Close log
 *Input :null
 *Output: use_stdout, fp
 *Return Value: null 
 */
void log_term(void)
{
	if (!g_log.fp)
	{
		return ;
	}

	if (!g_log.use_stdout)
	{
		fclose(g_log.fp);
	}

	g_log.use_stdout = 0;
	g_log.fp = NULL;

	return ;
}

/*
 *Function Introduction: Empty log
 *Input: res
 *Output: null
 *Return Value: null 
 */
int log_cleanup(int res)
{
	int   size;
	fseek(g_log.fp, 0, SEEK_END);
	size = ftell(g_log.fp);
	g_log.currsize = (unsigned int)size >= 0 ? size : 0;

	if ((unsigned int)res > (g_log.maxsize - g_log.currsize))
	{
		truncate(g_log.filename1, 0);
		fseek(g_log.fp, 0, SEEK_SET);
	}

	if (((unsigned int)res + g_log.currsize) <= g_log.maxsize)
	{
		return 0;
	}

	return 0;
}

/*
 *Function Introduction: Write record
 *Input: level, *format, args
 *Output: null
 *Return Value: null 
 */
void log_generic(const int level, const char* format, va_list args)
{
	char                  message[256];
	struct tm*            current_tm;
	time_t                time_now;

	vsprintf(message, format, args);

	time(&time_now);
	current_tm = localtime(&time_now);

	int res = fprintf(g_log.fp, "%s : %02i:%02i:%02i [%s] %s\n"
			, PROGRAM_NAME
			, current_tm->tm_hour
			, current_tm->tm_min
			, current_tm->tm_sec
			, LOG_LEVELS[level]
		       	, message);

	log_cleanup(res);

	fflush(g_log.fp);
}

/*
 *Function Introduction: Record error information
 *Input: ...
 *Output: null
 *Return Value:null 
 */
void log_error(char *format, ...)
{
	va_list   args;
	va_start(args, format);
	log_generic(LOG_LEVEL_ERROR, format, args);
	va_end(args);
}

/*
 *Function Introduction: Record warn information
 *Input: ...
 *Output: null
 *Return Value: null 
 */
void log_warn(char *format, ...)
{
	if (g_log.loglevel < LOG_LEVEL_WARN)
	{
		return ;
	}

	va_list args;
	va_start(args, format);
	log_generic(LOG_LEVEL_WARN, format, args);
	va_end(args);
}

/*
 *Function Introduction: Record error information
 *Input: ...
 *Output: null
 *Return Value:null 
 */
void log_info(char *format, ...)
{
	if (g_log.loglevel < LOG_LEVEL_INFO)
	{
		return ;
	}

	va_list args;
	va_start(args, format);
	log_generic(LOG_LEVEL_INFO, format, args);
	va_end(args);
}

/*
 *Function Introduction: Record debug information
 *Input: ...
 *Output: null
 *Return Value: null 
 */
void log_debug(char *format, ...)
{
	if (g_log.loglevel < LOG_LEVEL_DEBUG)
	{
		return ;
	}

	va_list args;
	va_start(args, format);
	log_generic(LOG_LEVEL_DEBUG, format, args);
	va_end(args);
}
